package com.zhx.common.websocket;


import com.zhx.common.websocket.handle.MyHeaderCollectHandler;
import com.zhx.common.websocket.handle.NettyWebSocketServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;


@Slf4j
@Configuration
public class NettyWebSocketServer {
    public static final int WEB_SOCKET_PORT = 8090;
    public static final NettyWebSocketServerHandler NETTY_WEB_SOCKET_SERVER_HANDLER = new NettyWebSocketServerHandler();

    // 创建线程池执行器
    //监听网络链接
    private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    //负责后续的I/O操作，如读取数据、写入数据等。
    private EventLoopGroup workerGroup = new NioEventLoopGroup(NettyRuntime.availableProcessors());

    /**
     * 启动 ws server
     * 在项目创建时调用
     *
     * @return
     * @throws InterruptedException
     */
    @PostConstruct
    public void start() throws InterruptedException {
        run();
    }

    /**
     * 销毁
     */
    @PreDestroy
    public void destroy() {
        //优雅关闭
        Future<?> future = bossGroup.shutdownGracefully();
        Future<?> future1 = workerGroup.shutdownGracefully();
        future.syncUninterruptibly();
        future1.syncUninterruptibly();
        log.info("关闭 ws server 成功");
    }


    public void run() throws InterruptedException {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(bossGroup, workerGroup);
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {

            //链接建立后被调用
            @Override
            protected void initChannel(NioSocketChannel socketChannel) throws Exception {
                ChannelPipeline pipeline = socketChannel.pipeline();
                //30秒客户端没有向服务器发送心跳则关闭连接 本地测试时可以关闭
                pipeline.addLast(new IdleStateHandler(100, 0, 0));
                // 因为使用http协议，所以需要使用http的编码器，解码器
                pipeline.addLast(new HttpServerCodec());
                // 以块方式写，添加 chunkedWriter 处理器
                pipeline.addLast(new ChunkedWriteHandler());
                /**
                 * 说明：
                 *  1. http数据在传输过程中是分段的，HttpObjectAggregator可以把多个段聚合起来；
                 *  2. 这就是为什么当浏览器发送大量数据时，就会发出多次 http请求的原因
                 */
                pipeline.addLast(new HttpObjectAggregator(8192));
                //保存用户ip
//                pipeline.addLast(new HttpHeadersHandler());

                /**
                 * 获取请求头中的token,获取ip
                 */
                pipeline.addLast(new MyHeaderCollectHandler());

                /**
                 * 说明：
                 *  1. 对于 WebSocket，它的数据是以帧frame 的形式传递的；
                 *  2. 可以看到 WebSocketFrame 下面有6个子类
                 *  3. 浏览器发送请求时： ws://localhost:7000/hello 表示请求的uri
                 *  4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议，保持长连接；
                 *      是通过一个状态码 101 来切换的
                 *      下面表示根路径下的请求都会转成ws协议
                 */
                pipeline.addLast(new WebSocketServerProtocolHandler("/"));

                /**
                 * 重写握手逻辑 实现token传参 没有采用
                 */
//                pipeline.addLast(new MyHandShakeHandler());

                // 自定义handler ，处理业务逻辑
                pipeline.addLast(new NettyWebSocketServerHandler());
            }
        });

        // 启动服务器，监听端口，阻塞直到启动成功
        serverBootstrap.bind(WEB_SOCKET_PORT).sync();
        System.out.println("启动成功");
    }
}
